﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Threading;
using System.Xml.Linq;
using SlamCms.Common;
using SlamCms.SharePointServices.Configuration;
using SlamCms.SharePointServices.Lists;

namespace SlamCms.SharePointServices
{
	public static class DocumentLibraryService
	{
		public static ServiceResponse<int> AddItem(string libraryUrl, string fileName, Stream file)
		{
			return AddItem(libraryUrl, fileName, file, null, false);
		}

		public static ServiceResponse<int> AddItem(string libraryUrl, string fileName, Stream file, ISharePointServiceItem item)
		{
			return AddItem(libraryUrl, fileName, file, item.ToServiceXml(), true);
		}

		private static ServiceResponse<int> AddItem(string libraryUrl, string fileName, Stream file, XElement fieldsElement, bool updateItem)
		{
			string listName = libraryUrl.TrimEnd('/').Substring(libraryUrl.TrimEnd('/').LastIndexOf("/") + 1);
			return AddItem(libraryUrl, listName, fileName, file, fieldsElement, updateItem);
		}

		private static ServiceResponse<int> AddItem(string libraryUrl, string libraryName, string fileName, Stream file, XElement fieldsElement, bool updateItem)
		{
			string fileUrl = libraryUrl.TrimEnd('/') + "/" + fileName.TrimStart('/');
			HttpWebRequest fileRequest = ServiceClients.CreateAuthenticatedRequest(fileUrl);
			fileRequest.Method = "PUT";
			fileRequest.Headers.Add("Overwrite", "T");
			fileRequest.Timeout = Timeout.Infinite;

			byte[] bytes = new byte[1024];
			using (Stream requestStream = fileRequest.GetRequestStream())
			{
				int bytesRead = 1024;
				while (bytesRead > 0)
				{
					bytesRead = file.Read(bytes, 0, bytes.Length);
					if (bytesRead > 0)
					{
						requestStream.Write(bytes, 0, bytesRead);
					}
				}
			}
			file.Dispose();

			WebResponse uploadResponse = fileRequest.GetResponse();

			ListsSoapClient client = ServiceClients.GetListServiceClient(libraryUrl.TrimEnd('/').Substring(0, libraryUrl.TrimEnd('/').LastIndexOf('/')));
			OperationContextScope scope = new OperationContextScope(client.InnerChannel);

			if (!String.IsNullOrEmpty(ConfigurationManager.Environment.NetworkCredential.ClaimsAuthenticationType))
			{
				var httpRequestProperty = new HttpRequestMessageProperty();
				httpRequestProperty.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
				OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
			}

			int itemId = 0;


			XElement query = new XElement("Query", new XElement("Where", new XElement("Eq", new XElement("FieldRef", new XAttribute("Name", "FileLeafRef")), new XElement("Value", new XAttribute("Type", "Text"), new XText(fileName)))));
			XElement viewFields = new XElement("ViewFields", new XElement("FieldRef", new XAttribute("Name", "ID")));
			XElement queryOptions = new XElement("QueryOptions", new XElement("ViewAttributes", new XAttribute("Scope", "Recursive")));

			XElement itemsReturned = client.GetListItems(libraryName, null, query, viewFields, "1", queryOptions, null);

			if (itemsReturned.Descendants(XName.Get("row", "#RowsetSchema")).Count() > 0)
			{
				int.TryParse(itemsReturned.Descendants(XName.Get("row", "#RowsetSchema")).FirstOrDefault().Attribute("ows_ID").Value, out itemId);
			}

			XElement batchElement = null;

			if (fieldsElement != null && fieldsElement.Descendants("Field").Count() > 0)
			{
				XElement methodElement = new XElement("Method", new XAttribute("ID", "1"),
															   new XAttribute("Cmd", "Update"));

				methodElement.Add(fieldsElement.Descendants("Field").ToArray<XElement>());
				if (!methodElement.Descendants("Field").Any(f => f.Attribute("Name").Value == "ID") && itemId > 0)
				{
					methodElement.Add(new XElement("Field", new XAttribute("Name", "ID"), new XText(itemId.ToString())));
				}

				batchElement = new XElement("Batch", new XAttribute("OnError", "Continue"),
															   new XAttribute("PreCalc", "TRUE"));
				batchElement.Add(methodElement);
			}

			if (updateItem && batchElement != null)
			{
				client.UpdateListItems(libraryName, batchElement);
			}

			client.CheckInFile(fileUrl, "", "1");

			scope.Dispose();

			ServiceResponse<int> response = new ServiceResponse<int>();
			response.Response = itemId;

			return response;
		}

		public static ServiceResponse DeleteFile(string libraryUrl, string fileName)
		{
			string listName = libraryUrl.TrimEnd('/').Substring(libraryUrl.TrimEnd('/').LastIndexOf("/") + 1);
			return DeleteFile(libraryUrl, listName, fileName);
		}

		public static ServiceResponse DeleteFile(string libraryUrl, Guid listId, string fileName)
		{
			return DeleteFile(libraryUrl, listId.ToString("B"), fileName);
		}

		public static ServiceResponse DeleteFile(string libraryUrl, string listName, string fileName)
		{
			string fileUrl = libraryUrl.TrimEnd('/') + "/" + fileName.TrimStart('/');
			ListsSoapClient client = ServiceClients.GetListServiceClient(libraryUrl.TrimEnd('/').Substring(0, libraryUrl.TrimEnd('/').LastIndexOf('/')));
			OperationContextScope scope = new OperationContextScope(client.InnerChannel);

			if (!String.IsNullOrEmpty(ConfigurationManager.Environment.NetworkCredential.ClaimsAuthenticationType))
			{
				var httpRequestProperty = new HttpRequestMessageProperty();
				httpRequestProperty.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f");
				OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = httpRequestProperty;
			}

			int itemId = 0;

			XElement query = new XElement("Query", new XElement("Where", new XElement("Eq", new XElement("FieldRef", new XAttribute("Name", "FileLeafRef")), new XElement("Value", new XAttribute("Type", "Text"), new XText(fileName)))));
			XElement viewFields = new XElement("ViewFields", new XElement("FieldRef", new XAttribute("Name", "ID")));
			XElement queryOptions = new XElement("QueryOptions", new XElement("ViewAttributes", new XAttribute("Scope", "Recursive")));

			XElement itemsReturned = client.GetListItems(listName, null, query, viewFields, "1", queryOptions, null);

			if (itemsReturned.Descendants(XName.Get("row", "#RowsetSchema")).Count() > 0)
			{
				int.TryParse(itemsReturned.Descendants(XName.Get("row", "#RowsetSchema")).FirstOrDefault().Attribute("ows_ID").Value, out itemId);
			}

			ServiceResponse response = new ServiceResponse();
			if (itemId > 0)
			{
				XElement batchElement = new XElement("Batch", new XAttribute("OnError", "Continue"),
										new XElement("Method", new XAttribute("ID", "1"), new XAttribute("Cmd", "Delete"),
											new XElement("Field", new XAttribute("Name", "ID"), new XText(itemId.ToString())),
											new XElement("Field", new XAttribute("Name", "FileRef"), new XText(fileUrl))));

				XElement itemsUpdated = client.UpdateListItems(listName, batchElement);
				XElement errorTextElement = itemsUpdated.Descendants(XName.Get("ErrorText", "http://schemas.microsoft.com/sharepoint/soap/")).FirstOrDefault();
				if (errorTextElement != null)
				{
					response.MessageCode = ServiceResponseMessageCode.Error;
					response.Message = String.Format("Failed to delete item, {0}.  Error: {1}", itemId, errorTextElement.Value);
				}
			}
			else
			{
				response.MessageCode = ServiceResponseMessageCode.Error;
				response.Message = String.Format("Item for file, {0}, not found.", fileName);
			}

			scope.Dispose();

			return response;
		}

		public static ServiceResponse DeleteItem(string libraryUrl, int listRelativeId)
		{
			return ListService.DeleteItem(libraryUrl, listRelativeId);
		}

		public static ServiceResponse<FileData> GetFileData(string url)
		{
			var serviceResponse = new ServiceResponse<FileData>();

			try
			{
				using (var response = GetResponse(url))
				{
					if (response != null && response.StatusCode == HttpStatusCode.OK)
					{
						serviceResponse.MessageCode = ServiceResponseMessageCode.None;
						serviceResponse.Response = new FileData()
						{
							ContentType = response.ContentType,
							ContentLength = response.ContentLength
						};

						serviceResponse.Response.Tag = response.Headers["ETag"] ?? string.Empty;
						var lastModifiedHeader = response.Headers["Last-Modified"] ?? string.Empty;
						var lastModified = DateTime.UtcNow;
						DateTime.TryParse(lastModifiedHeader, out lastModified);
						serviceResponse.Response.LastModified = lastModified;

						using (var responseStream = response.GetResponseStream())
						{
							if (responseStream != null)
							{
								var memoryStream = new MemoryStream();
								responseStream.CopyTo(memoryStream);

								responseStream.Flush();
								responseStream.Close();

								memoryStream.Seek(0, SeekOrigin.Begin);
								serviceResponse.Response.Content = memoryStream;
							}
						}
					}
					else
					{
						serviceResponse.MessageCode = ServiceResponseMessageCode.Error;
						serviceResponse.Message = response != null ? response.StatusDescription : string.Empty;
					}

					if (response != null)
						response.Close();
				}
			}
			catch (WebException ex)
			{
				serviceResponse.MessageCode = ServiceResponseMessageCode.Error;
				serviceResponse.Message = ex.Message;
			}

			return serviceResponse;
		}

		public static ServiceResponse<Stream> GetFile(string url)
		{
			var serviceResponse = new ServiceResponse<Stream>();

			using (var response = GetResponse(url))
			{
				if (response != null && response.StatusCode == HttpStatusCode.OK)
				{
					using (var responseStream = response.GetResponseStream())
					{
						if (responseStream != null)
						{
							var memoryStream = new MemoryStream();
							responseStream.CopyTo(memoryStream);

							responseStream.Flush();
							responseStream.Close();

							memoryStream.Seek(0, SeekOrigin.Begin);
							serviceResponse.Response = memoryStream;
						}
					}
				}
				else
					serviceResponse.MessageCode = ServiceResponseMessageCode.Error;

				if (response != null)
					response.Close();
			}

			return serviceResponse;
		}

		private static HttpWebResponse GetResponse(string url)
		{
			var fileRequest = ServiceClients.CreateAuthenticatedRequest(url);
			fileRequest.Accept = "*/*";
			fileRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.0.3705)";
			//fileRequest.Headers.Add("Accept-Encoding", "gzip,deflate");
			fileRequest.Method = "GET";
			fileRequest.KeepAlive = false;
			fileRequest.Timeout = 5000;
			fileRequest.ServicePoint.ConnectionLeaseTimeout = 5000;
			fileRequest.ServicePoint.MaxIdleTime = 5000;
			fileRequest.ServicePoint.ConnectionLimit = 50;

			return fileRequest.GetResponse() as HttpWebResponse;
		}

		public static ServiceResponse<T> GetItem<T>(string libraryUrl, int listRelativeId)
		{
			return ListService.GetItem<T>(libraryUrl, listRelativeId);
		}

		public static ServiceResponse<T> GetItem<T>(string libraryUrl, string contentItemId)
		{
			return ListService.GetItem<T>(libraryUrl, contentItemId);
		}

		public static ServiceResponse<IEnumerable<T>> GetItems<T>(string libraryUrl)
		{
			return ListService.GetItems<T>(libraryUrl);
		}

		public static ServiceResponse UpdateItem(string libraryUrl, int listRelativeId, ISharePointServiceItem item)
		{
			return ListService.UpdateItem(libraryUrl, listRelativeId, item);
		}

		public static ServiceResponse UpdateItem(string libraryUrl, Stream file, int listRelativeId, ISharePointServiceItem item)
		{
			return ListService.UpdateItem(libraryUrl, listRelativeId, item);
		}

		public static ServiceResponse UpdateItemModerationStatus(string libraryUrl, int listRelativeId, ModerationStatus status)
		{
			return ListService.UpdateItemModerationStatus(libraryUrl, listRelativeId, status);
		}

		public static ServiceResponse UpdateItemModerationStatus(string libraryUrl, Guid libraryId, int listRelativeId, ModerationStatus status)
		{
			return ListService.UpdateItemModerationStatus(libraryUrl, libraryId, listRelativeId, status);
		}

		public static ServiceResponse UpdateItemModerationStatus(string libraryUrl, string libraryName, int listRelativeId, ModerationStatus status)
		{
			return ListService.UpdateItemModerationStatus(libraryUrl, libraryName, listRelativeId, status);
		}
	}
}